﻿using System;
using System.ComponentModel;
using System.Diagnostics;

namespace sonic.Samples.Infrastructure.ViewModels
{
    /// <summary>
    /// Base class for all ViewModel classes in the application.
    /// It provides support for property change notifications 
    /// and has a DisplayName property.  This class is abstract.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable, IViewModelBase
    {

        #region Fields
        private string _DisplayName;
        private string _ViewName = Guid.NewGuid().ToString();
        private bool _IsEnabled = true;
        private bool _IsReadOnly = false;
        private bool _IsLoading = false;
        #endregion

        #region Constructor

        public ViewModelBase() {
        }

        #endregion // Constructor

        #region Public Properties
        /// <summary>
        /// Returns the user-friendly name of this object.
        /// Child classes can set this property to a new value,
        /// or override it to determine the value on-demand.
        /// </summary>
        public virtual string DisplayName {
            get { return _DisplayName; }
            set {
                _DisplayName = value;
                this.NotifyPropertyChanged("DisplayName");
            }
        }

        public virtual bool IsEnabled {
            get { return _IsEnabled; }
            set {
                if (_IsEnabled != value) {
                    _IsEnabled = value;
                    this.NotifyPropertyChanged("IsEnabled");
                    this.NotifyPropertyChanged("IsReadOnly");
                    this.RefreshCommands();
                }
            }
        }

        public virtual bool IsReadOnly {
            get { return _IsReadOnly || this.IsLoading || !this.IsEnabled; }
            set {
                if (_IsReadOnly != value) {
                    _IsReadOnly = value;
                    this.NotifyPropertyChanged("IsReadOnly");
                    this.RefreshCommands();
                }
            }
        }

        public virtual bool IsLoading {
            get { return _IsLoading; }
            set {
                if (_IsLoading != value) {
                    _IsLoading = value;
                    this.NotifyPropertyChanged("IsLoading");
                    this.NotifyPropertyChanged("IsReadOnly");
                    this.RefreshCommands();
                }
            }
        }

        /// <summary>
        /// Used to Identify the vew instance uniquely.
        /// Override this if you want a fixed view name 
        /// to register this Workspace against a region.
        /// </summary>
        public virtual string ViewName {
            get { return _ViewName; }
            protected set { _ViewName = value; }
        }

        #endregion

        /// <summary>
        /// Override this method to notify the ui 
        /// for command parammeters used as triggers.
        /// Use this on property setters when a value that 
        /// influences the CanExecute of a command is altered.
        /// For reference implementation see:
        /// IsLoading, IsReadonly, IsEnabled property setters.
        /// </summary>
        protected virtual void RefreshCommands() {

        }

        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This 
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName) {
            //// Verify that the property name matches a real,  
            //// public, instance property on this object.
            //if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            //{
            //    string msg = "Invalid property name: " + propertyName;

            //    if (this.ThrowOnInvalidPropertyName)
            //        throw new Exception(msg);
            //    else
            //        Debug.Fail(msg);
            //}
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void NotifyPropertyChanged(string propertyName) {
            this.VerifyPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null) {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region IDisposable Members

        /// <summary>
        /// Invoked when this object is being removed from the application
        /// and will be subject to garbage collection.
        /// </summary>
        public void Dispose() {
            this.OnDispose();
        }

        /// <summary>
        /// Child classes can override this method to perform 
        /// clean-up logic, such as removing event handlers.
        /// </summary>
        protected virtual void OnDispose() {
        }

#if DEBUG
        /// <summary>
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        /// </summary>
        ~ViewModelBase() {
            string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
            System.Diagnostics.Debug.WriteLine(msg);
        }
#endif

        #endregion // IDisposable Members
    }
}
